#include <iostream>
#include <algorithm>
using namespace std;

//写在开头：下面是利用并查集原理通过Kruscal算法实现最小生成树权值的计算，但得保证图是连通的！

int N,M;

//并查集的父亲数组
int father[5005];

//边结构体的定义
struct edg{
    int start;
    int terminal;
    int weight;
} edges[200005];

//下面是两个并查集的函数
int find_father(int x){
    if(father[x] != x) return find_father(father[x]);
    else return x; 
}

void unionn(int x, int y){
    father[find_father(y)] = find_father(x);
}

//对sort排序的第三参数的定义
bool cmp(edg a, edg b){
    return a.weight < b.weight;
}

int main(){
    cin>>N>>M;

    //初始化并查集数组
    for(int i=0;i<N;++i){
        father[i] = i;
    }
    
    int cnt = 0;
    int x,y,z;

    //读入边，要注意去除自环（重边可以通过并查集去掉）
    for(int i=0;i<M;++i){
        cin>>x>>y>>z;
        //x-=1;
        //y-=1;  如果点是从1开始的，加上这个
        if(x == y) continue;
        else{
            edges[cnt].start = x;
            edges[cnt].terminal = y;
            edges[cnt].weight = z;
            cnt+=1;
        }
    }

    //对结构体数组排序
    sort(edges, edges+cnt, cmp);

    int k=0;    //记录已加入的边的数目，当达到N-1时，终至循环
    int dis=0;
    for(int i=0;i<cnt;++i){
        if(k == N-1) break;
        if(find_father(edges[i].start) != find_father(edges[i].terminal)){
            unionn(find_father(edges[i].start), find_father(edges[i].terminal));
            dis += edges[i].weight;
            k++;
        }   
    }

    cout<<dis<<endl;  //输出答案

    return 0;
}